Double-Buffering

  • It's a technique, not a synchronization primitive.

  • Interesting when you have frequent reads and infrequent writes (e.g., rendering data, physics state).

  • How It Works :

    • Maintain two buffers: a front buffer (for readers) and a back buffer (for writers).

    • Readers always access the front buffer (no locks needed).

    • Writers modify the back buffer, then atomically swap the buffers when done.

#include <atomic>
#include <mutex>

struct GameState {
    int player_health;
    float enemy_positions[1000];
    // ... other game data
};

// Double-buffered data
std::atomic<GameState*> front_buffer;  // Readers access this (lock-free)
GameState back_buffer;                 // Writers modify this
std::mutex write_mutex;               // Protects back_buffer modifications

// Initialize
void Init() {
    front_buffer.store(new GameState(), std::memory_order_relaxed);
}

// Read Job (Lock-Free)
void ReadJob() {
    GameState* current = front_buffer.load(std::memory_order_acquire);
    // Safely read from 'current' (no locks needed!)
    int health = current->player_health;
    // ...
}

// Write Job (Synchronized)
void WriteJob() {
    std::lock_guard<std::mutex> lock(write_mutex);
    
    // Modify the back buffer
    back_buffer.player_health = 100;
    
    // Swap buffers (atomic, readers see the new state immediately)
    GameState* old = front_buffer.exchange(&back_buffer, std::memory_order_release);
    
    // Optional: Reuse 'old' for next write to avoid allocations
    std::swap(back_buffer, *old);
}

// Cleanup
void Shutdown() {
    delete front_buffer.load();
}
  • Scenario: Writer Removes an Element in Double Buffering

    1. Initial State:

      • front_buffer  β†’ [A, B, C, D]  (readers see this)

      • back_buffer  β†’ [A, B, C, D]  (writer modifies this)

    2. Writer Removes C :

      • Writer modifies back_buffer  β†’ [A, B, D]

      • Writer swaps frontbuffer  and backbuffer  (now frontbuffer  points to A, B, D ).

    3. Reader Access:

      • Before Swap: A reader sees A, B, C, D  (old frontbuffer ).

      • After Swap: New readers see A, B, D  (updated frontbuffer ).

    • What If a Reader Was Accessing C  When It Was Removed?

      • If a reader was iterating over frontbuffer  (old buffer: A, B, C, D ) while the swap happened, it still sees C  because:

        • The reader holds a pointer/reference to the old frontbuffer  (not affected by the swap).

        • The writer’s changes only affect new readers (those accessing frontbuffer  after the swap).

  • Is This Safe?

    • Yes:

      • Readers see a consistent snapshot (old buffer remains valid until they finish).

      • No data races (the swap is atomic, and old buffers aren’t modified).

    • BUT there are Memory Leak Risks :

      • If the old front_buffer  is discarded, readers holding references to it may access freed memory.

      • Strategy :

        • Manage lifetimes collectively.

          • Only "mark something as dead" without freeing it.

          • The free is done when no one is trying to read.